package com.store.satring.web;
import com.store.satring.dos.Customer;
import com.store.satring.dos.Invoice;
import com.store.satring.dos.Item;
import com.store.satring.dos.ItemBalanceDetail;
import com.store.satring.dos.ItemDetail;
import com.store.satring.dos.ItemReturnDetail;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.joda.time.format.DateTimeFormat;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils;

@RequestMapping("/invoices")
@Controller
public class InvoiceController {

	@RequestMapping(params = { "find=ByInvoiceNoEquals", "form" }, method = RequestMethod.GET)
    public String findInvoicesByInvoiceNoEqualsForm(Model uiModel) {
        return "invoices/findInvoicesByInvoiceNoEquals";
    }

	@RequestMapping(params = "find=ByInvoiceNoEquals", method = RequestMethod.GET)
    public String findInvoicesByInvoiceNoEquals(@RequestParam("invoiceNo") String invoiceNo, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, @RequestParam(value = "sortFieldName", required = false) String sortFieldName, @RequestParam(value = "sortOrder", required = false) String sortOrder, Model uiModel) {
            int sizeNo = size == null ? 10 : size.intValue();
            List<Invoice> findInvoicesByInvoiceNoEquals = Invoice.findInvoicesByInvoiceNoEquals(invoiceNo);
            uiModel.addAttribute("invoices", findInvoicesByInvoiceNoEquals);
            float nrOfPages = (float) findInvoicesByInvoiceNoEquals.size() / sizeNo;
            uiModel.addAttribute("maxPages", (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages));
        addDateTimeFormatPatterns(uiModel);
        return "invoices/list";
    }

	@RequestMapping(method = RequestMethod.POST, produces = "text/html")
    public String create(@Valid Invoice invoice, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
        if (bindingResult.hasErrors()) {
            populateEditForm(uiModel, invoice);
            return "invoices/create";
        }
        uiModel.asMap().clear();
        invoice.persist();
        return "redirect:invoices/"+invoice.getId()+"?form";
    }

	@RequestMapping(params = "form", produces = "text/html")
    public String createForm(Model uiModel) {
		Invoice invoice = new Invoice();
		invoice.setInvoiceNo(Invoice.getNewInvoiceNo());
		invoice.setInvoiceDate(new Date());
        populateEditForm(uiModel, invoice);
        List<String[]> dependencies = new ArrayList<String[]>();
        if (Customer.countCustomers() == 0) {
            dependencies.add(new String[] { "customer", "customers" });
        }
        uiModel.addAttribute("dependencies", dependencies);
        return "invoices/create";
    }

	@RequestMapping(value = "/{id}", produces = "text/html")
    public String show(@PathVariable("id") Long id, Model uiModel) {
        addDateTimeFormatPatterns(uiModel);
        uiModel.addAttribute("invoice", Invoice.findInvoice(id));
        uiModel.addAttribute("itemId", id);
        return "invoices/show";
    }

	@RequestMapping(produces = "text/html")
    public String list(@RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, @RequestParam(value = "sortFieldName", required = false) String sortFieldName, @RequestParam(value = "sortOrder", required = false) String sortOrder, Model uiModel) {
        if (page != null || size != null) {
            int sizeNo = size == null ? 10 : size.intValue();
            final int firstResult = page == null ? 0 : (page.intValue() - 1) * sizeNo;
            uiModel.addAttribute("invoices", Invoice.findInvoiceEntries(firstResult, sizeNo, sortFieldName, sortOrder));
            float nrOfPages = (float) Invoice.countInvoices() / sizeNo;
            uiModel.addAttribute("maxPages", (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages));
        } else {
            uiModel.addAttribute("invoices", Invoice.findAllInvoices(sortFieldName, sortOrder));
        }
        addDateTimeFormatPatterns(uiModel);
        return "invoices/list";
    }

	@RequestMapping(method = RequestMethod.PUT, produces = "text/html")
    public String update(@Valid Invoice invoice, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
        if (bindingResult.hasErrors()) {
            populateEditForm(uiModel, invoice);
            return "invoices/update";
        }
        Invoice findInvoice = Invoice.findInvoice(invoice.getId());
        
        uiModel.asMap().clear();
        findInvoice.setInvoiceNo(invoice.getInvoiceNo());
        findInvoice.setInvoiceDate(invoice.getInvoiceDate());
        findInvoice.setCustomer(invoice.getCustomer());
        findInvoice.setSiteFor(invoice.getSiteFor());
        findInvoice.setDescription(invoice.getDescription());
        findInvoice.setRate(invoice.getRate());
        findInvoice.setArea(invoice.getArea());
        findInvoice.setAmount(invoice.getAmount());
        findInvoice.setAdvanceAmount(invoice.getAdvanceAmount());
        
        findInvoice.merge();
        return "redirect:/invoices/" + encodeUrlPathSegment(invoice.getId().toString(), httpServletRequest);
    }
	
	/**
	 *Invoked when user click on Save button of 'Store Inventory' form
	 */
	@RequestMapping(method = RequestMethod.PUT, params="updateStoreInventory", produces = "text/html")
    public String updateStoreInventory(@Valid Invoice invoice, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
        if (bindingResult.hasErrors()) {
            populateEditForm(uiModel, invoice);
            return "redirect:/invoices?formUpdateStoreInventory";
        }
        Invoice findInvoice = Invoice.findInvoice(invoice.getId());
        uiModel.asMap().clear();
        findInvoice.setInvoiceNo(invoice.getInvoiceNo());
        findInvoice.setInvoiceDate(invoice.getInvoiceDate());
        findInvoice.setCustomer(invoice.getCustomer());
        findInvoice.setSiteFor(invoice.getSiteFor());
        findInvoice.setDescription(invoice.getDescription());
        findInvoice.setRate(invoice.getRate());
        findInvoice.setArea(invoice.getArea());
        findInvoice.setAmount(invoice.getAmount());
        findInvoice.setAdvanceAmount(invoice.getAdvanceAmount());
        findInvoice.merge();
        return "redirect:/invoices?formUpdateStoreInventory";
    }

	@RequestMapping(value = "/{id}", params = "form", produces = "text/html")
    public String updateForm(@PathVariable("id") Long id, Model uiModel, HttpServletRequest httpServletRequest) {
		Invoice invoice = Invoice.findInvoice(id);
		
		List<ItemDetail> itemDetails = invoice.getItemDetails() == null ? new ArrayList<ItemDetail>() : invoice.getItemDetails();
		List<ItemReturnDetail> itemReturnDetails = invoice.getItemReturnDetails() == null ? new ArrayList<ItemReturnDetail>() : invoice.getItemReturnDetails();
		
		Map<Item, Integer> map = new HashMap<Item, Integer>();
		for (ItemDetail itemDetail : itemDetails) {
			if (map.keySet().contains(itemDetail.getItem())) {
				map.put(itemDetail.getItem(), map.get(itemDetail.getItem())
						+ (itemDetail.getQuantity() == null ? 0 : itemDetail.getQuantity()));
			} else {
				map.put(itemDetail.getItem(), (itemDetail.getQuantity() == null ? 0 : itemDetail.getQuantity()));
			}
		}
		for (ItemReturnDetail itemReturnDetail : itemReturnDetails) {
			if(map.containsKey(itemReturnDetail.getItem())){
				Integer integer = map.get(itemReturnDetail.getItem());
				map.put(itemReturnDetail.getItem(), integer - (itemReturnDetail.getQuantity() == null ? 0 : itemReturnDetail.getQuantity()));
			} else {
				map.put(itemReturnDetail.getItem(), - (itemReturnDetail.getQuantity() == null ? 0 : itemReturnDetail.getQuantity()));
			}
		}
		List<ItemBalanceDetail> updated_itemBalanceDetails = new ArrayList<ItemBalanceDetail>(map.size());
		Set<Item> keySet = map.keySet();
		for (Item item : keySet) {
			ItemBalanceDetail itemBalanceDetail = new ItemBalanceDetail();
			itemBalanceDetail.setItem(item);
			if(map.get(item) == null || map.get(item) == 0){
				continue;
			}
			itemBalanceDetail.setQuantity(map.get(item));
			updated_itemBalanceDetails.add(itemBalanceDetail);
		}
		
		
		//get the list of Item which are same in new and old entries and update the quantity
		List<Long> processedItems = new ArrayList<Long>();
		List<ItemBalanceDetail> saved_TtemBalanceDetails = invoice.getItemBalanceDetails();
		for (ItemBalanceDetail saved_ItemBalanceDetail : saved_TtemBalanceDetails) {
			Item item = saved_ItemBalanceDetail.getItem();
			for (ItemBalanceDetail itemBalanceDetail : updated_itemBalanceDetails) {
				if(itemBalanceDetail.getItem().getId().equals(item.getId())){
					saved_ItemBalanceDetail.setQuantity(itemBalanceDetail.getQuantity());
					processedItems.add(item.getId());
					break;
				}
			}
		}
		//get the new ItemBalanceDetail. These are need to add in invoice.ItemBalanceDetail		
		List<ItemBalanceDetail> newItemBalanceDetails = new ArrayList<ItemBalanceDetail>();
		for (ItemBalanceDetail itemBalanceDetail : updated_itemBalanceDetails) {
			if(!processedItems.contains(itemBalanceDetail.getItem().getId())){
				newItemBalanceDetails.add(itemBalanceDetail);
				processedItems.add(itemBalanceDetail.getItem().getId());
			}
		}
		//get the ItemBalanceDetail which should be deleted
		List<ItemBalanceDetail> toBeRemoveItemBalanceDetails = new ArrayList<ItemBalanceDetail>();
		for (ItemBalanceDetail saved_ItemBalanceDetail : saved_TtemBalanceDetails) {
			if(!processedItems.contains(saved_ItemBalanceDetail.getItem().getId())){
				toBeRemoveItemBalanceDetails.add(saved_ItemBalanceDetail);
			}
		}
		// remove/add the ItemBalanceDetail
		saved_TtemBalanceDetails.removeAll(toBeRemoveItemBalanceDetails);
		saved_TtemBalanceDetails.addAll(newItemBalanceDetails);
		
		//Calculate Area and Price
		double area = 0;
		for (ItemDetail itemDetail : itemDetails) {
			if(itemDetail.getItem().getItemType().isConsiderForAreaCalculation()){
				double length = itemDetail.getItem().getLength();
				double width = itemDetail.getItem().getWidth();
				Integer quantity = itemDetail.getQuantity();
				area += (length * width * quantity);  
			}
		}
		
		invoice.setArea(area);
		invoice.setAmount(area * invoice.getRate());
		
		invoice.merge();
		
		//delete extra ItemBalanceDetail from DB
		for(ItemBalanceDetail itemBalanceDetail : toBeRemoveItemBalanceDetails){
			itemBalanceDetail.remove();
		}
		
		invoice = Invoice.findInvoice(id);
		
        populateEditForm(uiModel, invoice);
        httpServletRequest.getSession().removeAttribute("StoreInvoiceURL");
        return "invoices/update";
    }

	/**
	 *Invoked when user click on 'Store Inventory' URL
	 */
	@RequestMapping(params = "formUpdateStoreInventory", produces = "text/html")
    public String updateStoreInventory(Model uiModel, HttpServletRequest httpServletRequest) {
		Invoice invoice = Invoice.findStoreInvoice();
		if(invoice == null){
			//Create an Invoice for Store Inventory
			invoice = new Invoice();
			invoice.setInvoiceNo(999999);
			invoice.setInvoiceDate(new Date());
			invoice.setCustomer(Customer.findStoreCustomer());
			invoice.persist();
			invoice = Invoice.findStoreInvoice();
		}
		
		List<ItemDetail> itemDetails = invoice.getItemDetails() == null ? new ArrayList<ItemDetail>() : invoice.getItemDetails();
		List<ItemReturnDetail> itemReturnDetails = invoice.getItemReturnDetails() == null ? new ArrayList<ItemReturnDetail>() : invoice.getItemReturnDetails();
		
		Map<Item, Integer> map = new HashMap<Item, Integer>();
		for (ItemDetail itemDetail : itemDetails) {
			if (map.keySet().contains(itemDetail.getItem())) {
				map.put(itemDetail.getItem(), map.get(itemDetail.getItem())
						+ (itemDetail.getQuantity() == null ? 0 : itemDetail.getQuantity()));
			} else {
				map.put(itemDetail.getItem(), (itemDetail.getQuantity() == null ? 0 : itemDetail.getQuantity()));
			}
		}
		for (ItemReturnDetail itemReturnDetail : itemReturnDetails) {
			if(map.containsKey(itemReturnDetail.getItem())){
				Integer integer = map.get(itemReturnDetail.getItem());
				map.put(itemReturnDetail.getItem(), integer - (itemReturnDetail.getQuantity() == null ? 0 : itemReturnDetail.getQuantity()));
			} else {
				map.put(itemReturnDetail.getItem(), - (itemReturnDetail.getQuantity() == null ? 0 : itemReturnDetail.getQuantity()));
			}
		}
		List<ItemBalanceDetail> updated_itemBalanceDetails = new ArrayList<ItemBalanceDetail>(map.size());
		Set<Item> keySet = map.keySet();
		for (Item item : keySet) {
			ItemBalanceDetail itemBalanceDetail = new ItemBalanceDetail();
			itemBalanceDetail.setItem(item);
			if(map.get(item) == null || map.get(item) == 0){
				continue;
			}
			itemBalanceDetail.setQuantity(map.get(item));
			updated_itemBalanceDetails.add(itemBalanceDetail);
		}
		
		//get the list of Item which are same in new and old entries and update the quantity
		List<Long> processedItems = new ArrayList<Long>();
		List<ItemBalanceDetail> saved_TtemBalanceDetails = invoice.getItemBalanceDetails();
		for (ItemBalanceDetail saved_ItemBalanceDetail : saved_TtemBalanceDetails) {
			Item item = saved_ItemBalanceDetail.getItem();
			for (ItemBalanceDetail itemBalanceDetail : updated_itemBalanceDetails) {
				if(itemBalanceDetail.getItem().getId().equals(item.getId())){
					saved_ItemBalanceDetail.setQuantity(itemBalanceDetail.getQuantity());
					processedItems.add(item.getId());
					break;
				}
			}
		}
		//get the new ItemBalanceDetail. These are need to add in invoice.ItemBalanceDetail		
		List<ItemBalanceDetail> newItemBalanceDetails = new ArrayList<ItemBalanceDetail>();
		for (ItemBalanceDetail itemBalanceDetail : updated_itemBalanceDetails) {
			if(!processedItems.contains(itemBalanceDetail.getItem().getId())){
				newItemBalanceDetails.add(itemBalanceDetail);
				processedItems.add(itemBalanceDetail.getItem().getId());
			}
		}
		//get the ItemBalanceDetail which should be deleted
		List<ItemBalanceDetail> toBeRemoveItemBalanceDetails = new ArrayList<ItemBalanceDetail>();
		for (ItemBalanceDetail saved_ItemBalanceDetail : saved_TtemBalanceDetails) {
			if(!processedItems.contains(saved_ItemBalanceDetail.getItem().getId())){
				toBeRemoveItemBalanceDetails.add(saved_ItemBalanceDetail);
			}
		}
		// remove/add the ItemBalanceDetail
		saved_TtemBalanceDetails.removeAll(toBeRemoveItemBalanceDetails);
		saved_TtemBalanceDetails.addAll(newItemBalanceDetails);

		
		//Calculate Area and Price
		double area = 0;
		for (ItemBalanceDetail itemBalanceDetail : updated_itemBalanceDetails) {
			if(itemBalanceDetail.getItem().getItemType().isConsiderForAreaCalculation()){
				double length = itemBalanceDetail.getItem().getLength();
				double width = itemBalanceDetail.getItem().getWidth();
				Integer quantity = itemBalanceDetail.getQuantity();
				area += (length * width * quantity);  
			}
		}

		invoice.setArea(area);
		
		invoice.merge();
		
		//delete extra ItemBalanceDetail from DB
		for(ItemBalanceDetail itemBalanceDetail : toBeRemoveItemBalanceDetails){
			itemBalanceDetail.remove();
		}
		
		invoice = Invoice.findInvoice(invoice.getId());
		
        populateEditForm(uiModel, invoice);
        //Special handling for customer (store = 1)
        List<Customer> customerList = new ArrayList<Customer>();
        customerList.add(Customer.findStoreCustomer());
        uiModel.addAttribute("customers", customerList);
        uiModel.addAttribute("id", invoice.getId());
        //Handling to return correct page when ItemDetail created from Store Invoice
        httpServletRequest.getSession().setAttribute("StoreInvoiceURL", "formUpdateStoreInventory");
        return "invoices/updateStoreInventory";
    }

	@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "text/html")
    public String delete(@PathVariable("id") Long id, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, Model uiModel) {
        Invoice invoice = Invoice.findInvoice(id);
        invoice.remove();
        uiModel.asMap().clear();
        uiModel.addAttribute("page", (page == null) ? "1" : page.toString());
        uiModel.addAttribute("size", (size == null) ? "10" : size.toString());
        return "redirect:/invoices";
    }

	void addDateTimeFormatPatterns(Model uiModel) {
        uiModel.addAttribute("invoice_invoicedate_date_format", DateTimeFormat.patternForStyle("M-", LocaleContextHolder.getLocale()));
        uiModel.addAttribute("invoice_createdon_date_format", DateTimeFormat.patternForStyle("M-", LocaleContextHolder.getLocale()));
        uiModel.addAttribute("invoice_updatedon_date_format", DateTimeFormat.patternForStyle("M-", LocaleContextHolder.getLocale()));
    }

	void populateEditForm(Model uiModel, Invoice invoice) {
        uiModel.addAttribute("invoice", invoice);
        addDateTimeFormatPatterns(uiModel);
        uiModel.addAttribute("customers", Customer.findAllCustomers());
        uiModel.addAttribute("itemdetails", invoice.getItemDetails());
        uiModel.addAttribute("itemreturndetails", invoice.getItemReturnDetails());
        uiModel.addAttribute("itembalancedetails", invoice.getItemBalanceDetails());
    }

	String encodeUrlPathSegment(String pathSegment, HttpServletRequest httpServletRequest) {
        String enc = httpServletRequest.getCharacterEncoding();
        if (enc == null) {
            enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
        }
        try {
            pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
        } catch (UnsupportedEncodingException uee) {}
        return pathSegment;
    }
}
